排序算法及并行分析

本文探讨了冒泡排序的并行版本——奇偶排序,虽然它在实际并行应用中效率较低,但对于理解并行算法有一定价值。此外,文章还介绍了插入排序、希尔排序和归并排序的基本思想,并提到了快速排序的实现。作者通过实验对比了不同排序算法的性能和并行加速比,并强调了并行设计时应注意的问题,如算法扩展性、能耗效率、线程粒度控制和内存管理开销。
摘要由CSDN通过智能技术生成
最近学了高性能计算这门课程,老师让用OpenMP、MPI或mapReduce写个大作业。我之前刚好在写排序,于是我就将常用的排序写了一遍并且用OpenMP进行并行,计算加速比等数据进行分析。在这篇文章中我主要介绍八大基本排序的实现原理及代码,以及对这些算法进行改进从而让它们可以并行,并且对他们的性能进行了比较。首先跟大家分享一下我的心得体会,所谓排序算法,就是通过调整元素的位置达到想要的结果。我们需要明确这个排序算法的概念,也就是排序的思想。明白这个之后,在进行算法设计是,我是这样思考的:这个排序算法有多少趟排序(这里指的是大的排序),也就是最外层的for循环有多少次;然后每一趟是从哪里开始?怎么进行元素的交换?有了这个思路之后,排序算法就好写多了,当然我是这样认为的,每个人都有不同的思维方式。对于像归并、快排这样用递归实现起来较为方便的,我认为把出栈、入栈的顺序理清,理解起来就容易多了。其实,只有自己动手实践了,发现了问题并解决了或者说虽然没有解决,但是可以问老师、同学,并且确实对算法有了更深的理解,这无疑是很有意义的。这里我都是先列举排序算法的大致思路,然后直接贴上代码,代码几乎没注释,一是算法完全可以看懂,而是希望大家在有了自己的思路基础上再去看代码或动手写代码,你会发现自己对算法的思路更加明晰了。这里我用到了openmp进行并行,如果只是想看排序算法的,可以直接把并行部分忽略。
</pre><pre name="code" class="cpp">首先是一些头文件和宏定义,如下:
#include "stdlib.h"
#include "iostream"
#include "omp.h"
#include "time.h"
#include "vector"
#include "stack"
using namespace std;

//#define random(x) (rand()%x)
#define BOUNDARY 1000000000	//定义随机数产生的区间
#define MAX_NUM 1000000	//随机数组的元素个数
const double MinProb = 1.0 / (RAND_MAX + 1);	//概率
typedef int KeyInt;
//定义一个记录待排序的区间[low,high]  
typedef struct Region
{
	int low;
	int high;
}Region;

 

 

 

 

 

下面是我写的一些函数:

 

KeyInt* randomCreate(int N);
bool happened(double probability);
int myrandom(int n);//产生0~n-1之间的等概率随机数
void DisPlay(int N, KeyInt *p);
KeyInt* BubbleAlgorithm(int N, KeyInt *p);//冒泡排序
KeyInt* BubbleAlgorithmParallel(int N, KeyInt *p);//奇偶排序
KeyInt* InsertSort(int N, KeyInt *p);//插入排序
KeyInt* InsertSort(int *p, int low, int high);//指定区间插入排序
vector<KeyInt> InsertSortPart(int N, KeyInt *p);//分区间插入排序
vector<KeyInt> InsertSortParallel(int N, KeyInt *p);//插入排序并行
vector<KeyInt> InsertVector(vector<KeyInt> &vec, int value);
vector<KeyInt> InsertVectorSort(vector<KeyInt> &vec); 
KeyInt* ShellSort(int N, KeyInt *p);//希尔排序
KeyInt* ShellSortParallel(int N, KeyInt *p);//希尔排序并行
KeyInt* InsertSort(int N, KeyInt *p, int start, int inc);//指定起始点和步长进行插入排序
void MergeSort(KeyInt *p, KeyInt *temp, int l, int r);//归并排序
void MergeSort(KeyInt *p, int N);//非递归归并排序
void MergeSortParallel(KeyInt *p, KeyInt *temp, int l, int r);//2核归并排序
void MergeSortParallel(KeyInt *p, KeyInt *temp, int N);//4核归并排序
void MergeSortParallel(KeyInt *p, int N);//并行非递归归并排序
void Merge(KeyInt *p, KeyInt *temp, int l, int r);//归并
void QuickSort(KeyInt *p, int low, int high);//快排
void QuickSortAverage(KeyInt *p, int low, int high);//快排+三数取中+插入
void QuickSortSame(KeyInt *p, int low, int high);//快排+三数取中+插入+聚集相等元素
int SelectPivotMedianOfThree(int *arr, int low, int high);//三数取中
int Partition(int * a, int low, int high);//分隔
void NonRecursiveQuickSort(int *a, int len);//用栈实现快排
void QuickSortParallel(KeyInt *p, int low, int high);//2核快排
void QuickSortParallel4Core(KeyInt *p, int low, int high);//4核快排
KeyInt* BubbleAlgorithm(int N, KeyInt *p)	//冒泡排序
{
	int i, j;
	KeyInt temp;
//#pragma omp parallel for
	for (i = 0; i<N-1; i++)
		for (j = 0; j<N-1-i; j++)
			if (p[j]>p[j+1])
			{
				temp = p[j];
				p[j] = p[j+1];
				p[j+1] = temp;
			}
	return (p);
}
 

冒泡排序比较简单,每趟排序从前往后依次比较相邻的两个元素,使小的在前,大的在后,经过n-1次排序即可完成。我们可以看出for循环执行了n*(n-1)/2次,所以时间复杂度是o(n^2)。另外,冒泡排序是不可以直接进行并行的,因为前面排序的结果会对后面的排序产生影响,所以我们需要对它进行改进。下面,我介绍冒泡的并行版本,奇偶排序(Odd-even Sort)。

KeyInt* BubbleAlgorithmParallel(int N, KeyInt *p)	//奇偶排序Odd-even Sort
{
	int i, j;
	for (i = 1; i < N; i++) {
		if ((i&0x1) == 1) {
#pragma omp parallel for
			for (j = 0; j < N - 1; j += 2) {
				if (p[j] > p[j + 1]) {
					int temp = p[j];
					p[j] = p[j + 1];
					p[j + 1] = temp;
				}
			}
		}
		else {
#pragma omp parallel for
			for (j = 2; j < N; j += 2) {
				if (p[j-1] > p[j]) {
					int temp = p[j-1];
					p[j-1] = p[j];
					p[j] = temp;
				}
			}
		}
	}
	return (p);
}

奇偶排序是冒泡排序的并行化版本,其主要思想是奇数次排序比较奇数位和它后面一位的大小,偶数次排序比较奇数位和其前面一位的大小。这里的#pragma omp parallel for 是openmp的并行语句,表示紧跟其后的for循环开多个线程并行。如果只是

看排序算法,可以自动忽略。

 

 

 

上图为odd-even sort的基本方法。 
奇数步中, array中奇数项array[i]与右边的item(array[i + 1])比较; 
偶数步中, array中奇数项array[i]与左边的item(array[i - 1]) 比较;

奇偶排序在实际中用来并行并没有意义,因为每次循环都需要进行线程的创建和销毁,你会发现这大大影响了算法的效率,甚至开了并行后更慢了。冒泡排序只是针对小数据量的排序,比如元素个数小于一万的数组,所以奇偶排序用来并行并没有实际意义,仅有学习价值。

 

KeyInt* InsertSort(int N, KeyInt *p)//插入排序
{
	int temp;
	for (int i = 1; i < N; i++) {
		for (int j = i; (j > 0) && (p[j] < p[j - 1]); j--) {
			temp = p[j];
			p[j] = p[j - 1];
			p[j - 1] = temp;
		}
	}
	return p;
}

 插入排序基本思想
 将n个元素的数列分为已有序和无序两个部分,如插入排序过程示例下所示:   
  { {a1},{a2,a3,a4,…,an}}   
 { {a1⑴,a2⑴},{a3⑴,a4⑴ …,an⑴}}  
 { {a1(n-1),a2(n-1) ,…},{an(n-1)}}   
  每次处理就是将无序数列的第一个元素与有序数列的元素从后往前逐个进行比较,
 找出插入位置,将该元素插入到有序数列的合适位置中。

 

 

KeyInt* InsertSort(int *p, int low, int high)//指定区间插入排序,即对数组p的指定位置进行插入排序
{
	int temp;
	for (int i = low+1; i <= high; i++) {
		for (int j = i; (j > low) && (p[j] < p[j - 1]); j--) {
			temp = p[j];
			p[j] = p[j - 1];
			p[j - 1] = temp;
		}
	}
	return p;
}

vector<KeyInt> InsertVector(vector<KeyInt> &vec, int value) {	//vector类型插入排序
	if (vec.size() &#
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值